import * as THREE from "../../../modules/three/three.module.js";
import {arrAsubB, removeArrayItem} from "../util/StaticMethods.js";
import {randBetween} from "../util/Utils.js";
import {ConvexObjectBreaker} from "../../../modules/three/jsm/misc/ConvexObjectBreaker.js";
import {d2r} from "../lib/CommonConsts.js";

const ShapeEnum = {
  brick: 'brick',
  ball: 'ball',
  cylinder: 'cylinder',
  capsule: 'capsule', // 胶囊体
  tetrahedron: 'tetrahedron', // 正四面体
  octahedron: 'octahedron', // 正八面体
}
const randTypes = [ShapeEnum.ball, ShapeEnum.brick, ShapeEnum.cylinder, ShapeEnum.capsule, ShapeEnum.tetrahedron, ShapeEnum.octahedron, ShapeEnum.ball]
// const randTypes = [ShapeEnum.ball, ShapeEnum.brick, ShapeEnum.ball]

export const StatusEnum = {
  none: 'none',
  breaking: 'breaking', // 被子弹击中
  disappearing: 'disappearing', // 打中无人机
  missing: 'missing', // 被躲避
}
const missingDistance = 200

const mtl = new THREE.MeshPhongMaterial({color: '#8c6621', transparent: true})

class Breakable {
  // 初始位置
  pos = new THREE.Vector3()
  // 不为空时，不跟随无人机
  velocity
  _hitSpan
  // 跟随无人机的位移速度
  followSpeed = 0.3
  // 自旋速度
  speedRot = new THREE.Euler()
  shape = ShapeEnum.brick
  shapeParams = []
  getHitSpan() {
    if (this._hitSpan) {
      return this._hitSpan
    }
    this._hitSpan = this.velocity.length() * 2
    const ballLike = [ShapeEnum.ball, ShapeEnum.tetrahedron, ShapeEnum.octahedron]
    if (ballLike.includes(this.shape)) {
      this._hitSpan = Math.max(this._hitSpan, this.shapeParams[0])
    }
    return this._hitSpan
  }
}

const sphere = new THREE.Sphere()
const _raycaster = new THREE.Raycaster()
const _plane = new THREE.Plane()
const _v0 = new THREE.Vector3()
export function getRaycasterFromBullet(bulletEntity) {
  const {geometry, position, dir} = bulletEntity
  const len = geometry.parameters.length
  const ray = _raycaster.ray
  ray.origin.copy(dir).negate().multiplyScalar(0.5).add(position)
  ray.direction.copy(dir)
  _raycaster.far = len
  return _raycaster
}
const convexBreaker = new ConvexObjectBreaker();

const _v2 = new THREE.Vector3()
export class DebrisItem {
  /**
   * @type {THREE.Mesh}
   */
  mesh
  /**
   * @type {THREE.Vector3}
   */
  dir
  rot = new THREE.Euler()
  /**
   * @type {number[]}
   */
  amounts
  constructor(mesh, direction, amounts) {
    this.mesh = mesh
    this.dir = direction
    this.amounts = amounts
    const rot = this.rot
    const aa = ~~(Math.random() * 4)
    rot.x = randBetween(0.01, 0.3)
    if (aa === 2) {
      rot.y = randBetween(0, 0.15)
    } else if (aa === 1) {
      rot.z = randBetween(0, 0.15)
    } else if (aa === 0) {
      rot.x = 0
    }
  }
  updateDebris() {
    if (!this.amounts.length) {
      return false
    }
    _v2.copy(this.dir).multiplyScalar(this.amounts.shift())
    this.mesh.position.add(_v2)
    this.mesh.rotation.x += this.rot.x
    this.mesh.rotation.y += this.rot.y
    this.mesh.rotation.z += this.rot.z
    return true
  }
}

class SimpleBreakableEntity extends THREE.Mesh {
  /**
   * @type {Breakable}
   */
  vo
  hitPoint = new THREE.Vector3()
  hitNormal = new THREE.Vector3()
  /**
   * @type {null | BulletEntity}
   */
  hitByBullet
  status = StatusEnum.none
  dir = new THREE.Vector3(0, 1, 0)
  stay = 35

  constructor(breakable) {
    super();
    this.vo = breakable
    this.position.copy(breakable.pos)
    let geometry = new THREE.BoxGeometry()
    switch (breakable.shape) {
      case ShapeEnum.brick:
        const [width, height, deep] = breakable.shapeParams
        this.scale.set(width, height, deep)
        break
      case ShapeEnum.ball:
        const [radius] = breakable.shapeParams
        geometry = new THREE.SphereGeometry(radius, 16, 16)
        break;
      case ShapeEnum.cylinder:
        const [len, radiusDown, radiusUp] = breakable.shapeParams
        geometry = new THREE.CylinderGeometry(radiusUp, radiusDown, len)
        break;
      case ShapeEnum.capsule:
        geometry = new THREE.CapsuleGeometry(0.5, 1)
        break;
      case ShapeEnum.tetrahedron:
        const [radius1] = breakable.shapeParams
        geometry = new THREE.TetrahedronGeometry(radius1)
        break;
      case ShapeEnum.octahedron:
        const [radius2] = breakable.shapeParams
        geometry = new THREE.OctahedronGeometry(radius2)
        break;
    }
    this.geometry = geometry
    this.material = mtl.clone()
    convexBreaker.prepareBreakableObject( this, 100, new THREE.Vector3(), new THREE.Vector3(), true );
  }

  //被子弹击中后，产生的若干碎片
  breakToPieces(emitter) {
    this.material.color.setStyle('#fc6207')
    const debris = convexBreaker.subdivideByImpact(this, this.hitPoint, this.hitNormal, 1, 2)
    console.log('breakToPieces, ', debris.length)
    const pos = this.position
    debris.forEach(it => {
      let dir
      if (it.position.equals(pos)) {
        dir = this.hitNormal.clone().negate()
      } else {
        dir = it.position.clone().sub(pos).normalize()
      }
      const amount = randBetween(0.05, 0.75)
      const amounts = []
      for (let i = 0; i <= 90; i += 1) {
        const a = amount * Math.cos(i * d2r)
        amounts.push(a)
      }
      it.isDebris = true
      const debrisItem = new DebrisItem(it, dir, amounts)
      emitter.debrisFromBreakable.push(debrisItem)
      emitter.scene.add(it)
    })
    return debris
  }

  disposeBreakable(breakables) {
    this.removeFromParent()
    removeArrayItem(breakables, this)
  }
  tick(aircraft, emitter) {
    if (this.status !== StatusEnum.none) {
      this.stay--
      if (this.status === StatusEnum.breaking) {
        this.material.opacity = this.stay / 35.0
      }
      if (this.stay <= 0 || this.status === StatusEnum.missing) {
        this.disposeBreakable(emitter.targets)
      }
      return;
    }
    const r = this.vo.speedRot
    this.rotation.x += r.x
    this.rotation.y += r.y
    this.rotation.z += r.z
    const len = this.position.distanceTo(aircraft.position)

    const velocity = this.vo.velocity
    if (velocity) {
      this.dir.copy(velocity)
      this.position.add(velocity)
      const distance = aircraft.position.distanceTo(this.position)
      if (distance < this.vo.getHitSpan()) {
        this.status = StatusEnum.disappearing
        console.log('SimpleBreakableEntity 命中无人机')
        aircraft.collided(this.dir)
        return
      }
    } else { // move towards aircraft
      const speed = this.vo.followSpeed
      if (len <= speed) {
        this.position.copy(aircraft.position)
        this.status = StatusEnum.disappearing
        console.log('SimpleBreakableEntity 命中无人机')
        aircraft.collided(this.dir)
        return
      }
      this.dir.copy(this.position)
      this.position.lerp(aircraft.position, speed / len)
      this.dir.sub(this.position).negate()
    }
    if (this.position.distanceTo(aircraft.position) > missingDistance) {
      this.status = StatusEnum.missing
      return
    }
    const bullets = aircraft.bullets
    for (let i = 0; i < bullets.length; i++) {
      if (this.hitTestWithBullet(bullets[i])) {
        this.status = StatusEnum.breaking
        // console.log('SimpleBreakableEntity 被子弹命中')
        this.breakToPieces(emitter)
        this.disposeBreakable(emitter.targets)
        return
      }
    }
  }

  hitTestWithBullet(bulletEntity) {
    const raycaster = getRaycasterFromBullet(bulletEntity)
    if (this.vo.shape === ShapeEnum.ball) {
      sphere.center.copy(this.position)
      sphere.radius = this.vo.shapeParams[0]
      const inter = raycaster.ray.intersectSphere(sphere, this.hitPoint)
      if (!inter) {
        return false
      }
      this.hitByBullet = bulletEntity
      bulletEntity.detachItem()
      this.hitNormal.copy(this.hitPoint).sub(this.position).normalize()
      return true
    }
    const intersect = raycaster.intersectObject(this, false)
    if (!intersect.length) {
      return false
    }
    this.hitByBullet = bulletEntity
    bulletEntity.detachItem()
    this.hitPoint.copy(intersect[0].point)
    // this.hitNormal.copy(intersect[0].face.normal).applyQuaternion(this.quaternion)
    this.hitNormal.copy(this.hitPoint).sub(this.position).normalize()
    return true
  }
}

export class BreakableEmitter {
  /**
   * @type {SimpleBreakableEntity[]}
   */
  targets = []
  pos = new THREE.Vector3(230, 300, 3)
  range = new THREE.Vector3(42, 28, 100)
  /**
   * @type {DebrisItem[]}
   */
  debrisFromBreakable = []
  scene

  constructor(scene) {
    this.scene = scene
  }

  tick4Debris() {
    const arr = this.debrisFromBreakable
    if (!arr.length) {
      return
    }
    const removing = []
    arr.forEach((debris, index) => {
      const updated = debris.updateDebris()
      if (!updated) {
        removing.push(debris)
        // console.log('remove debris, ', index)
        debris.mesh.removeFromParent()
      }
    })
    arrAsubB(arr, removing)
  }

  tick(aircraft) {
    if (!aircraft) {
      return;
    }
    this.tick4Debris()
    const dist = this.pos.distanceTo(aircraft.position)
    if (dist > missingDistance) {
      return;
    }
    this.targets.forEach(it => {
      it.tick(aircraft, this)
    })
    if (this.targets.length >= 5) {
      return;
    }

    const vo = new Breakable()
    vo.pos.x = this.pos.x + this.range.x * Math.random()
    vo.pos.y = this.pos.y + this.range.y * Math.random()
    const zVal = Math.max(aircraft.position.z - 2, 3)
    vo.pos.z = randBetween(zVal, aircraft.position.z + 5)

    const n = ~~(Math.random() * randTypes.length)
    vo.shape = randTypes[n]
    vo.followSpeed = randBetween(0.1, 0.25)
    vo.velocity = aircraft.position.clone().sub(vo.pos).normalize()
    vo.velocity.multiplyScalar(randBetween(0.15, 0.35))

    vo.speedRot.x = randBetween(0, 0.1)
    const aa = ~~(Math.random() * 4)
    if (aa === 2) {
      vo.speedRot.y = randBetween(0, 0.05)
    } else if (aa === 1) {
      vo.speedRot.z = randBetween(0, 0.05)
    } else if (aa === 0) {
      vo.speedRot.x = 0
    }

    switch (vo.shape) {
      case ShapeEnum.ball:
      case ShapeEnum.tetrahedron:
      case ShapeEnum.octahedron:
        vo.shapeParams[0] = randBetween(0.4, 1.2)
        break
      case ShapeEnum.brick:
        vo.shapeParams[0] = randBetween(0.5, 2)
        vo.shapeParams[1] = randBetween(0.6, 2.5)
        vo.shapeParams[2] = randBetween(0.8, 3)
        break
      case ShapeEnum.cylinder:
        const tt = ~~(Math.random() * 3)
        const len = randBetween(1.7, 5)
        const rB = randBetween(0.25, len * 0.55)
        if (tt === 0) {
          vo.shapeParams = [len, rB, rB]
        } else if (tt === 1) {
          vo.shapeParams = [len, rB, 0]
        } else {
          const rT = randBetween(0.25, len * 0.45)
          vo.shapeParams = [len, rB, rT]
        }
        break
    }
    const entity = new SimpleBreakableEntity(vo)
    this.scene.add(entity)
    this.targets.push(entity)
  }
}
